Skip to content

🛡️ Sentinel: [MEDIUM] Secure backup file permissions#36

Open
kidchenko wants to merge 1 commit intomainfrom
sentinel/secure-backup-permissions-9963695077044937017
Open

🛡️ Sentinel: [MEDIUM] Secure backup file permissions#36
kidchenko wants to merge 1 commit intomainfrom
sentinel/secure-backup-permissions-9963695077044937017

Conversation

@kidchenko
Copy link
Owner

@kidchenko kidchenko commented Feb 20, 2026

🛡️ Sentinel: [MEDIUM] Fix insecure file permissions on backups

Vulnerability: Backup archives created by tools/backup-projects.sh relied on default system umask, potentially leaving sensitive project backups world-readable (e.g., 644/755).

Impact: Local users could read the contents of backup archives, exposing source code, configuration files, and potentially secrets.

Fix:

  • Added umask 077 to cmd_backup to ensure all created files/directories are private by default.
  • Added explicit chmod 700 to the backup directory.
  • Added explicit chmod 600 to the backup zip archive.

Verification:

  • Added tests/verify_backup_permissions.sh which creates a mock backup and asserts that file permissions are 600 and directory permissions are 700.
  • Ran the test script successfully on Linux (assumed environment).

PR created automatically by Jules for task 9963695077044937017 started by @kidchenko

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Backup archives are now created with secure file permissions, preventing unintended access to sensitive backup data.
  • Tests

    • Added test validation for backup file and directory permissions across different operating systems.
  • Documentation

    • Added documentation detailing secure backup permissions best practices and prevention steps.

Explicitly set `umask 077` and use `chmod` to ensure backup archives and directories are private (600/700).
Added a regression test `tests/verify_backup_permissions.sh`.

Co-authored-by: kidchenko <5432753+kidchenko@users.noreply.github.com>
@google-labs-jules
Copy link
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@coderabbitai
Copy link

coderabbitai bot commented Feb 20, 2026

📝 Walkthrough

Walkthrough

This change suite implements security hardening for backup operations by enforcing restrictive file and directory permissions. A documentation file details the vulnerability, a test script validates permission enforcement, and the backup script is modified to apply umask 077 and explicit chmod 600/700 to archive files and backup directories.

Changes

Cohort / File(s) Summary
Backup Security Documentation
.jules/sentinel.md
New documentation detailing the umask vulnerability in backup archives, the inheritance of process umask by mkdir and zip, and explicit prevention steps using umask 077 and chmod restrictions.
Backup Permissions Test
tests/verify_backup_permissions.sh
New test script that validates backup file (600) and directory (700) permissions. Includes OS-specific stat command handling for Darwin vs. Linux, temporary directory setup with YAML config, and assertion checks with cleanup.
Backup Script Hardening
tools/backup-projects.sh
Implementation of secure permissions: applies umask 077 and chmod 700 to the temporary backup directory, and chmod 600 to the resulting archive file. Changes affect initialization and post-archive handling phases.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

Sensitive files tucked away safe,
With permissions strict—no world-read chafe.
Umask seventy-seven guards the burrow deep,
Tests verify backups that secrets keep! 🐰

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main security improvement: enforcing secure backup file permissions. It directly addresses the primary change across all modified files.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch sentinel/secure-backup-permissions-9963695077044937017

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
tools/backup-projects.sh (1)

353-357: umask 077 is process-global — it silently affects all subsequent file/directory creation.

umask changes apply to the calling process and are inherited by all child processes. Setting it here means every file/directory created after this point (log files in sync_git_repos, the LOG_DIR at line 357, zip temp files, etc.) will silently receive restrictive 600/700 permissions. This is benign for a personal backup script but obscures intent.

The LOG_DIR at line 357 will also be created as 700 with no explicit chmod, which may be surprising.

Consider saving and restoring the umask to scope the tightened mask to just the directory creation step:

♻️ Proposed scoping via save/restore
 if [[ "$DRY_RUN" != true ]]; then
     # Ensure secure permissions for backup directory (700)
+    _old_umask=$(umask)
     umask 077
     mkdir -p "$BACKUP_TEMP_DIR"
     chmod 700 "$BACKUP_TEMP_DIR"
+    umask "$_old_umask"
     mkdir -p "$LOG_DIR"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/backup-projects.sh` around lines 353 - 357, The script sets umask 077
globally which unintentionally tightens permissions for all subsequent creations
(affecting LOG_DIR, zip temp files, and functions like sync_git_repos); to fix,
scope the restrictive mask around just the backup dir creation by saving the
current umask, setting umask 077, creating/chmod-ing BACKUP_TEMP_DIR, then
restoring the saved umask before continuing so LOG_DIR and other files are
created with their intended permissions; reference the umask call and the
mkdir/chmod operations for BACKUP_TEMP_DIR and the subsequent mkdir for LOG_DIR
when applying this change.
tests/verify_backup_permissions.sh (1)

1-36: Several test robustness gaps.

Three issues worth addressing together:

  1. SCRIPT_PATH assumes pwd is the repo root (Line 9). Running the test from any other directory silently targets the wrong path or fails with no explanation. Use the script's own location instead:

    -SCRIPT_PATH="$(pwd)/tools/backup-projects.sh"
    +SCRIPT_PATH="$(cd "$(dirname "$0")/.." && pwd)/tools/backup-projects.sh"
  2. stderr from the backup script leaks to the terminal (Line 36). Warning messages (yq not installed, git output, etc.) pollute test output. Use >/dev/null 2>&1.

  3. set -e + no yq = silent, opaque failure (Lines 2 and 36). If yq is not installed the backup script falls back to default folders, archives nothing, exits 1, and the test exits immediately via set -e with no diagnostic. Add a prerequisite guard:

♻️ Proposed fixes
 #!/bin/bash
-set -e
+set -euo pipefail

 # Setup test paths
 TEST_DIR="$(mktemp -d)"
 TEST_SOURCE="$TEST_DIR/source"
 TEST_BACKUP_DIR="$TEST_DIR/backups"
 TEST_CONFIG="$TEST_DIR/config.yaml"
-SCRIPT_PATH="$(pwd)/tools/backup-projects.sh"
+SCRIPT_PATH="$(cd "$(dirname "$0")/.." && pwd)/tools/backup-projects.sh"

 # Cleanup on exit
 trap 'rm -rf "$TEST_DIR"' EXIT

+# Prerequisites
+if ! command -v yq &>/dev/null; then
+    echo "SKIP: yq is required but not installed"
+    exit 0
+fi
+
 ...

 # Run backup script
-"$SCRIPT_PATH" backup --config "$TEST_CONFIG" >/dev/null
+"$SCRIPT_PATH" backup --config "$TEST_CONFIG" >/dev/null 2>&1 || {
+    echo "FAIL: backup script exited non-zero"
+    exit 1
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/verify_backup_permissions.sh` around lines 1 - 36, The test assumes
SCRIPT_PATH via pwd, leaks stderr, and can fail silently under set -e if yq is
missing; fix by resolving the script path relative to the test file (use the
test script's directory / dirname reference to locate tools/backup-projects.sh
rather than pwd), add a prerequisite guard that checks for required binaries
(check for yq and any other required commands and print a clear error + exit if
missing), and suppress the backup script's stdout/stderr in the run invocation
by redirecting both stdout and stderr (use >/dev/null 2>&1) when calling
"$SCRIPT_PATH" backup --config "$TEST_CONFIG". Ensure these changes reference
SCRIPT_PATH, TEST_CONFIG, and the initial set -e behavior so the guard runs
before the script invocation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.jules/sentinel.md:
- Around line 1-4: The markdown fails markdownlint: change the top-level heading
from "## 2024-05-24 - Secure Backup Permissions" to a H1 ("# 2024-05-24 - Secure
Backup Permissions") and add a blank line after that heading (fixes MD041 and
MD022); then wrap or reflow the long description lines (the lines starting with
"**Vulnerability:**", "**Learning:**", and "**Prevention:**") so each line is
<=80 characters (break into multiple sentences/lines while preserving content
and bullets), and ensure any newly-wrapped lines maintain Markdown formatting
(keep the bold labels like **Vulnerability:** intact) to resolve the MD013
violations.

In `@tests/verify_backup_permissions.sh`:
- Around line 19-30: The test writes logs outside TEST_DIR because the generated
TEST_CONFIG lacks logging.dir, so LOG_DIR falls back to XDG_STATE_HOME and
cmd_backup/sync_git_repos create $LOG_DIR/git-repos.log unconditionally; update
the test config written to TEST_CONFIG to include a logging.dir set inside
TEST_DIR (e.g., "${TEST_DIR}/state" or similar) so that LOG_DIR resolves inside
TEST_DIR and all mkdir -p and log writes performed by cmd_backup and
sync_git_repos are contained and cleaned up by the trap.

In `@tools/backup-projects.sh`:
- Around line 431-432: The chmod 600 "$archive_path" call can fail silently
because the script doesn't enable errexit; update the script so the failure is
detected and causes the job to fail: either enable shell errexit (set -e) near
the top alongside set -o pipefail, or immediately check the exit status of chmod
600 "$archive_path" (or append an explicit failure handler) and log an error
referencing $archive_path then exit with non-zero; locate the chmod call and
implement one of these two fixes so permission errors aren't swallowed.

---

Nitpick comments:
In `@tests/verify_backup_permissions.sh`:
- Around line 1-36: The test assumes SCRIPT_PATH via pwd, leaks stderr, and can
fail silently under set -e if yq is missing; fix by resolving the script path
relative to the test file (use the test script's directory / dirname reference
to locate tools/backup-projects.sh rather than pwd), add a prerequisite guard
that checks for required binaries (check for yq and any other required commands
and print a clear error + exit if missing), and suppress the backup script's
stdout/stderr in the run invocation by redirecting both stdout and stderr (use
>/dev/null 2>&1) when calling "$SCRIPT_PATH" backup --config "$TEST_CONFIG".
Ensure these changes reference SCRIPT_PATH, TEST_CONFIG, and the initial set -e
behavior so the guard runs before the script invocation.

In `@tools/backup-projects.sh`:
- Around line 353-357: The script sets umask 077 globally which unintentionally
tightens permissions for all subsequent creations (affecting LOG_DIR, zip temp
files, and functions like sync_git_repos); to fix, scope the restrictive mask
around just the backup dir creation by saving the current umask, setting umask
077, creating/chmod-ing BACKUP_TEMP_DIR, then restoring the saved umask before
continuing so LOG_DIR and other files are created with their intended
permissions; reference the umask call and the mkdir/chmod operations for
BACKUP_TEMP_DIR and the subsequent mkdir for LOG_DIR when applying this change.

Comment on lines +1 to +4
## 2024-05-24 - Secure Backup Permissions
**Vulnerability:** Backup archives created by `zip` without explicit permissions were world-readable (depending on umask), exposing sensitive project data.
**Learning:** `mkdir -p` and `zip` respect the process `umask`, but relying on system defaults is insecure for sensitive data.
**Prevention:** Explicitly set `umask 077` at the start of sensitive operations and use `chmod` to enforce restrictive permissions (600/700) on critical artifacts.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix 5 markdownlint violations failing CI.

The Lint Documentation check is failing with five violations:

Line Rule Issue
1 MD041 First heading must be # (H1), not ## (H2)
1 MD022 Heading must be followed by a blank line
2 MD013 Line length 155 > 80
3 MD013 Line length 126 > 80
4 MD013 Line length 163 > 80
📝 Proposed fix
-## 2024-05-24 - Secure Backup Permissions
-**Vulnerability:** Backup archives created by `zip` without explicit permissions were world-readable (depending on umask), exposing sensitive project data.
-**Learning:** `mkdir -p` and `zip` respect the process `umask`, but relying on system defaults is insecure for sensitive data.
-**Prevention:** Explicitly set `umask 077` at the start of sensitive operations and use `chmod` to enforce restrictive permissions (600/700) on critical artifacts.
+# 2024-05-24 - Secure Backup Permissions
+
+**Vulnerability:** Backup archives created by `zip` without explicit permissions were world-readable
+(depending on umask), exposing sensitive project data.
+
+**Learning:** `mkdir -p` and `zip` respect the process `umask`, but relying on system defaults is
+insecure for sensitive data.
+
+**Prevention:** Explicitly set `umask 077` at the start of sensitive operations and use `chmod` to
+enforce restrictive permissions (600/700) on critical artifacts.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
## 2024-05-24 - Secure Backup Permissions
**Vulnerability:** Backup archives created by `zip` without explicit permissions were world-readable (depending on umask), exposing sensitive project data.
**Learning:** `mkdir -p` and `zip` respect the process `umask`, but relying on system defaults is insecure for sensitive data.
**Prevention:** Explicitly set `umask 077` at the start of sensitive operations and use `chmod` to enforce restrictive permissions (600/700) on critical artifacts.
# 2024-05-24 - Secure Backup Permissions
**Vulnerability:** Backup archives created by `zip` without explicit permissions were world-readable
(depending on umask), exposing sensitive project data.
**Learning:** `mkdir -p` and `zip` respect the process `umask`, but relying on system defaults is
insecure for sensitive data.
**Prevention:** Explicitly set `umask 077` at the start of sensitive operations and use `chmod` to
enforce restrictive permissions (600/700) on critical artifacts.
🧰 Tools
🪛 GitHub Check: Lint Documentation

[failure] 4-4: Line length
.jules/sentinel.md:4:81 MD013/line-length Line length [Expected: 80; Actual: 163] https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md013.md


[failure] 3-3: Line length
.jules/sentinel.md:3:81 MD013/line-length Line length [Expected: 80; Actual: 126] https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md013.md


[failure] 2-2: Line length
.jules/sentinel.md:2:81 MD013/line-length Line length [Expected: 80; Actual: 155] https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md013.md


[failure] 1-1: First line in a file should be a top-level heading
.jules/sentinel.md:1 MD041/first-line-heading/first-line-h1 First line in a file should be a top-level heading [Context: "## 2024-05-24 - Secure Backup ..."] https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md041.md


[failure] 1-1: Headings should be surrounded by blank lines
.jules/sentinel.md:1 MD022/blanks-around-headings Headings should be surrounded by blank lines [Expected: 1; Actual: 0; Below] [Context: "## 2024-05-24 - Secure Backup Permissions"] https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md022.md

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.jules/sentinel.md around lines 1 - 4, The markdown fails markdownlint:
change the top-level heading from "## 2024-05-24 - Secure Backup Permissions" to
a H1 ("# 2024-05-24 - Secure Backup Permissions") and add a blank line after
that heading (fixes MD041 and MD022); then wrap or reflow the long description
lines (the lines starting with "**Vulnerability:**", "**Learning:**", and
"**Prevention:**") so each line is <=80 characters (break into multiple
sentences/lines while preserving content and bullets), and ensure any
newly-wrapped lines maintain Markdown formatting (keep the bold labels like
**Vulnerability:** intact) to resolve the MD013 violations.

Comment on lines +19 to +30
cat > "$TEST_CONFIG" <<EOF
backup:
folders:
- $TEST_SOURCE
local:
base_dir: $TEST_BACKUP_DIR
retention_days: 1
remote:
enabled: false
logging:
enabled: false
EOF
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Test isolation broken: backup script writes git-repos.log outside TEST_DIR.

The test config sets logging.enabled: false but omits logging.dir. As a result, LOG_DIR defaults to ${XDG_STATE_HOME:-$HOME/.local/state}/dotfiles, which is outside TEST_DIR. Both cmd_backup and sync_git_repos unconditionally call mkdir -p "$LOG_DIR" and write $LOG_DIR/git-repos.log regardless of the LOGGING_ENABLED flag — those files land in the user's home directory and are not cleaned up by the trap. On a system where $HOME/.local/state/dotfiles doesn't yet exist, this also creates it with mode 700 (due to umask 077), potentially altering that directory's permissions permanently.

Add logging.dir to the test config to contain all I/O within TEST_DIR:

🐛 Proposed fix
 cat > "$TEST_CONFIG" <<EOF
 backup:
   folders:
     - $TEST_SOURCE
   local:
     base_dir: $TEST_BACKUP_DIR
     retention_days: 1
   remote:
     enabled: false
   logging:
     enabled: false
+    dir: $TEST_DIR/state
 EOF
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
cat > "$TEST_CONFIG" <<EOF
backup:
folders:
- $TEST_SOURCE
local:
base_dir: $TEST_BACKUP_DIR
retention_days: 1
remote:
enabled: false
logging:
enabled: false
EOF
cat > "$TEST_CONFIG" <<EOF
backup:
folders:
- $TEST_SOURCE
local:
base_dir: $TEST_BACKUP_DIR
retention_days: 1
remote:
enabled: false
logging:
enabled: false
dir: $TEST_DIR/state
EOF
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/verify_backup_permissions.sh` around lines 19 - 30, The test writes
logs outside TEST_DIR because the generated TEST_CONFIG lacks logging.dir, so
LOG_DIR falls back to XDG_STATE_HOME and cmd_backup/sync_git_repos create
$LOG_DIR/git-repos.log unconditionally; update the test config written to
TEST_CONFIG to include a logging.dir set inside TEST_DIR (e.g.,
"${TEST_DIR}/state" or similar) so that LOG_DIR resolves inside TEST_DIR and all
mkdir -p and log writes performed by cmd_backup and sync_git_repos are contained
and cleaned up by the trap.

Comment on lines +431 to +432
# Ensure secure permissions for backup archive (600)
chmod 600 "$archive_path"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

chmod 600 failure is silently swallowed.

The script sets set -o pipefail but not set -e, so a failing chmod (e.g., read-only filesystem, permission denied) goes undetected and the run reports success with a potentially mis-permissioned archive. Add explicit error handling:

🛡️ Proposed fix
-        # Ensure secure permissions for backup archive (600)
-        chmod 600 "$archive_path"
+        # Ensure secure permissions for backup archive (600)
+        if ! chmod 600 "$archive_path"; then
+            error "Failed to set secure permissions on archive: $archive_path"
+            return 1
+        fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Ensure secure permissions for backup archive (600)
chmod 600 "$archive_path"
# Ensure secure permissions for backup archive (600)
if ! chmod 600 "$archive_path"; then
error "Failed to set secure permissions on archive: $archive_path"
return 1
fi
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tools/backup-projects.sh` around lines 431 - 432, The chmod 600
"$archive_path" call can fail silently because the script doesn't enable
errexit; update the script so the failure is detected and causes the job to
fail: either enable shell errexit (set -e) near the top alongside set -o
pipefail, or immediately check the exit status of chmod 600 "$archive_path" (or
append an explicit failure handler) and log an error referencing $archive_path
then exit with non-zero; locate the chmod call and implement one of these two
fixes so permission errors aren't swallowed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant